/*
 * Copyright (c) 2007 T.Cogghe, D.Leroux, S.Lévignac, S.Rives
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#ifndef __FORME_H__
#define __FORME_H__

#include "Id.hh"
#include "Astre.hh"

/*! une exception déclenchable par la construction des formes */
class FormeException {
private:
	const char*issue;
public:
	FormeException(const char*tag) : issue(tag) {}
	const char*quoi() { return issue; }
};

/*! exception lancée quand une forme ressemble trop à une autre permutation d'elle-même */
class FormeSymetrique : public FormeException {
public: FormeSymetrique() : FormeException("La forme est trop symétrique ; Superpaws ne peut pas l'utiliser") {}
};

/*! exception lancée quand une forme est trop petite */
class FormeDegeneree : public FormeException {
public: FormeDegeneree() : FormeException("La forme est trop petite ; Superpaws ne peut pas l'utiliser") {}
};

/*! représente la forme engendrée par trois Astres dans une image, à une permutation près.
 */
class Forme {
public:
	Forme(const Forme&f) {
		p=f.p;
		astres[0]=f.astres[0];
		astres[1]=f.astres[1];
		astres[2]=f.astres[2];
		_id=new Identite(*f._id);
		_id->setRef(this);
	}

	/*! constructeur ; génère aussi l'identité de la forme, avec la permutation 0.
	 * la création d'une forme peut déclencher des exceptions.
	 */
	Forme(const Astre*a,const Astre*b,const Astre*c) throw(FormeDegeneree,FormeSymetrique) {
		p=0;
		astres[0]=a;
		astres[1]=b;
		astres[2]=c;
		try {
			_id=new Identite(*this);
		} catch(FormeException&e) {
			throw;
		}
	}

	~Forme() {
		delete _id;
	}

	/*! accès aux astres de la forme, avec la dernière permutation calculée */
	const Astre*ast(const int i) const { return astres[permutes[p][i]]; }

	/*! représente l'identité de la forme. */
	class Identite : public Id {
	private:
		double angles[3];
		double dists[3];
		Forme*f;

		inline double compare(const Identite&id,const int per) const {
			double dtot=1;
			for(int i=0;i<3;i++) {
				dtot *= 1 + Id::deltaPourcents( dists[permutes[per][i]], id.dists[i]);
				dtot *= 1 + Id::deltaPourcents(angles[permutes[per][i]], id.angles[i]);
			}
			return dtot;
		}

		/*! calcule la distance entre deux astres \par a et \par b. */
		inline double calcDist(const Astre*a,const Astre*b) const {
			double	dx=a->x()-b->x(),dy=a->y()-b->y();
			return sqrt(dx*dx+dy*dy);
		}

		/*! calcule l'angle formé par les vecteurs \par o -> \par a et \par o -> \par b. */
		inline double calcAngle(const Astre*o,const Astre*a,const Astre*b) const {
			/* calcule un produit scalaire, divise par les normes, qui dans tous les cas sont !0, ça fait un cosinus */
			double	dx1=a->x()-o->x(),dy1=a->y()-o->y();
			double	dx2=b->x()-o->x(),dy2=b->y()-o->y();
			return acos((dx1*dx2+dy1*dy2)/(calcDist(o,a)*calcDist(o,b)));
		}

	public:
		static double _tol;

		Identite(const Identite&i) : Id(_tol) {
			angles[0]=i.angles[0];
			angles[1]=i.angles[1];
			angles[2]=i.angles[2];
			dists[0]=i.dists[0];
			dists[1]=i.dists[1];
			dists[2]=i.dists[2];
		}

		void setRef(Forme*fm) { f=fm; }

		Identite(Forme&fm) throw(FormeDegeneree,FormeSymetrique) : Id(_tol), f(&fm) {
			dists[0]=calcDist(f->ast(1),f->ast(2));
			dists[1]=calcDist(f->ast(2),f->ast(0));
			dists[2]=calcDist(f->ast(0),f->ast(1));
			if(dists[0]<10||dists[1]<10||dists[2]<10) {
				throw FormeDegeneree();
			}
			angles[0]=calcAngle(f->ast(0),f->ast(1),f->ast(2));
			angles[1]=calcAngle(f->ast(1),f->ast(2),f->ast(0));
			angles[2]=calcAngle(f->ast(2),f->ast(0),f->ast(1));

			/* teste la symétrie de la forme */
			for(int i=1;i<6;i++) {
				if(compare(*this,i)<2*_tol) {
					//cerr<<"Forme trop symmétrique !"<<endl;
					throw FormeSymetrique();
				}
			}
		}
		~Identite() {}

		void dump() const {
			cout<<"Forme::Identite"<<endl;
			cout<<"| dists = [ "<<dists[0]<<", "<<dists[1]<<", "<<dists[2];
			cout<<" ]    angles = [ "<<angles[0]<<", "<<angles[1]<<", ";
			cout<<angles[2]<<" ]"<<endl;
			cout<<"| permutation #"<<f->p<<endl;
			cout<<"| dists = [ "<<dists[permutes[f->p][0]]<<", ";
			cout<<dists[permutes[f->p][1]]<<", "<<dists[permutes[f->p][2]];
			cout<<" ]    angles = [ "<<angles[permutes[f->p][0]];
			cout<<", "<<angles[permutes[f->p][1]]<<", ";
			cout<<angles[permutes[f->p][2]]<<" ]"<<endl;
		}
		
		/*! compare deux identités (prédicat "ressemble à") */
		bool operator == (const Id&ib) const {
			const Identite&i=*dynamic_cast<const Identite*>(&ib);
			double delta=(*this)%i;
			//cout<<"id's == ? : "<<delta<<endl;
			return delta>=-_tol&&delta<=_tol;
		}

		/*! estime l'éloignement entre deux identités */
		double operator % (const Id&ib) const {
			const Identite*id=dynamic_cast<const Identite*>(&ib);
			double dmin,dtmp;

			f->p=0;
			dmin=compare(*id,0);
			//cout<<"Comparaison de deux formes : "<<dmin;

			for(int i=1;i<6;i++) {
				dtmp=compare(*id,i);
				//cout<<' '<<dtmp;
				if(dtmp<dmin) {
					f->p=i;
					dmin=dtmp;
				}
			}

			//cout<<" - a retenu #"<<p<<" : "<<dmin<<endl;

			return dmin-1;
		}
	};

	Identite&id() const { return *_id; }

	void dump() const {
		_id->dump();
	}

private:
	static const int permutes[6][3];
	const Astre*astres[3];
	int p;			/* permutation */
	Identite*_id;
};



#endif

